/**
 * $Id: mxCellMarker.java,v 1.15 2010/01/29 09:07:01 gaudenz Exp $
 * Copyright (c) 2008, Gaudenz Alder
 */
package com.magnificent.panda.ui.handler;

import com.magnificent.graph.util.*;
import com.magnificent.graph.util.mxEventSource.mxIEventListener;
import com.magnificent.graph.view.mxCellState;
import com.magnificent.graph.view.mxGraphView;
import com.magnificent.panda.ui.GraphComponent;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;

/**
 * Implements a mouse tracker that marks cells under the mouse.
 * <p/>
 * This class fires the following event:
 * <p/>
 * mxEvent.MARK fires in mark and unmark to notify the listener of a new cell
 * under the mouse. The <code>state</code> property contains the mxCellState
 * of the respective cell or null if no cell is under the mouse.
 */
public class mxCellMarker extends JComponent {
    /**
     * Specifies if the highlights should appear on top of everything
     * else in the overlay pane. Default is false.
     */
    public static boolean KEEP_ON_TOP = false;

    /**
     *
     */
    private static final long serialVersionUID = 614473367053597572L;

    /**
     * Specifies the default stroke for the marker.
     */
    public static Stroke DEFAULT_STROKE = new BasicStroke(3);

    /**
     * Holds the event source.
     */
    protected mxEventSource eventSource = new mxEventSource(this);

    /**
     * Holds the enclosing graph component.
     */
    protected GraphComponent graphComponent;

    /**
     * Specifies if the marker is enabled. Default is true.
     */
    protected boolean enabled = true;

    /**
     * Specifies the portion of the width and height that should trigger
     * a highlight. The area around the center of the cell to be marked is used
     * as the hotspot. Possible values are between 0 and 1. Default is
     * mxConstants.DEFAULT_HOTSPOT.
     */
    protected double hotspot;

    /**
     * Specifies if the hotspot is enabled. Default is false.
     */
    protected boolean hotspotEnabled = false;

    /**
     * Specifies if the the content area of swimlane should be non-transparent
     * to mouse events. Default is false.
     */
    protected boolean swimlaneContentEnabled = false;

    /**
     * Specifies the valid- and invalidColor for the marker.
     */
    protected Color validColor, invalidColor;

    /**
     * Holds the current marker color.
     */
    protected transient Color currentColor;

    /**
     * Holds the marked state if it is valid.
     */
    protected transient mxCellState validState;

    /**
     * Holds the marked state.
     */
    protected transient mxCellState markedState;

    /**
     * Constructs a new marker for the given graph component.
     *
     * @param graphComponent
     */
    public mxCellMarker(GraphComponent graphComponent) {
        this(graphComponent, mxConstants.DEFAULT_VALID_COLOR);
    }

    /**
     * Constructs a new marker for the given graph component.
     */
    public mxCellMarker(GraphComponent graphComponent, Color validColor) {
        this(graphComponent, validColor, mxConstants.DEFAULT_INVALID_COLOR);
    }

    /**
     * Constructs a new marker for the given graph component.
     */
    public mxCellMarker(GraphComponent graphComponent, Color validColor,
                        Color invalidColor) {
        this(graphComponent, validColor, invalidColor,
                mxConstants.DEFAULT_HOTSPOT);
    }

    /**
     * Constructs a new marker for the given graph component.
     */
    public mxCellMarker(GraphComponent graphComponent, Color validColor,
                        Color invalidColor, double hotspot) {
        this.graphComponent = graphComponent;
        this.validColor = validColor;
        this.invalidColor = invalidColor;
        this.hotspot = hotspot;
    }

    /**
     * Sets the enabled state of the marker.
     */
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * Returns true if the marker is enabled, that is, if it processes events
     * in process.
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * Sets the hotspot.
     */
    public void setHotspot(double hotspot) {
        this.hotspot = hotspot;
    }

    /**
     * Returns the hotspot.
     */
    public double getHotspot() {
        return hotspot;
    }

    /**
     * Specifies whether the hotspot should be used in intersects.
     */
    public void setHotspotEnabled(boolean enabled) {
        this.hotspotEnabled = enabled;
    }

    /**
     * Returns true if hotspot is used in intersects.
     */
    public boolean isHotspotEnabled() {
        return hotspotEnabled;
    }

    /**
     * Sets if the content area of swimlanes should not be transparent to
     * events.
     */
    public void setSwimlaneContentEnabled(boolean swimlaneContentEnabled) {
        this.swimlaneContentEnabled = swimlaneContentEnabled;
    }

    /**
     * Returns true if the content area of swimlanes is non-transparent to
     * events.
     */
    public boolean isSwimlaneContentEnabled() {
        return swimlaneContentEnabled;
    }

    /**
     * Returns true if validState is not null.
     */
    public boolean hasValidState() {
        return (validState != null);
    }

    /**
     * Returns the valid state.
     */
    public mxCellState getValidState() {
        return validState;
    }

    /**
     * Returns the marked state.
     */
    public mxCellState getMarkedState() {
        return markedState;
    }

    /**
     * Resets the state of the cell marker.
     */
    public void reset() {
        validState = null;

        if (markedState != null) {
            markedState = null;
            unmark();
        }
    }

    /**
     * Processes the given event and marks the state returned by getStateAt
     * with the color returned by getMarkerColor. If the markerColor is not
     * null, then the state is stored in markedState. If isValidState returns
     * true, then the state is stored in validState regardless of the marker
     * color. The state is returned regardless of the marker color and
     * valid state.
     */
    public mxCellState process(MouseEvent e) {
        mxCellState state = null;

        if (isEnabled()) {
            state = getState(e);
            boolean isValid = (state != null) ? isValidState(state) : false;
            Color color = getMarkerColor(e, state, isValid);

            if (isValid) {
                validState = state;
            } else {
                validState = null;
            }

            if (state != markedState || color != currentColor) {
                currentColor = color;

                if (state != null && currentColor != null) {
                    markedState = state;
                    mark();
                } else if (markedState != null) {
                    markedState = null;
                    unmark();
                }
            }
        }

        return state;
    }

    /**
     * Marks the markedState and fires a mxEvent.MARK event.
     */
    protected void mark() {
        if (markedState != null) {
            Rectangle bounds = markedState.getRectangle();
            bounds.grow(3, 3);
            bounds.width += 1;
            bounds.height += 1;
            setBounds(bounds);

            if (getParent() == null) {
                setVisible(true);

                if (KEEP_ON_TOP) {
                    graphComponent.getGraphControl().add(this, 0);
                } else {
                    graphComponent.getGraphControl().add(this);
                }
            }

            repaint();
            eventSource.fireEvent(new mxEventObject(mxEvent.MARK, "state",
                    markedState));
        }
    }

    /**
     * Hides the marker and fires a mxEvent.MARK event.
     */
    protected void unmark() {
        if (getParent() != null) {
            setVisible(false);
            getParent().remove(this);
            eventSource.fireEvent(new mxEventObject(mxEvent.MARK));
        }
    }

    /**
     * Returns true if the given state is a valid state. If this returns true,
     * then the state is stored in validState. The return value of this method
     * is used as the argument for getMarkerColor.
     */
    protected boolean isValidState(mxCellState state) {
        return true;
    }

    /**
     * Returns the valid- or invalidColor depending on the value of isValid.
     * The given state is ignored by this implementation.
     */
    protected Color getMarkerColor(MouseEvent e, mxCellState state,
                                   boolean isValid) {
        return (isValid) ? validColor : invalidColor;
    }

    /**
     * Uses getCell, getMarkedState and intersects to return the state for
     * the given event.
     */
    protected mxCellState getState(MouseEvent e) {
        Object cell = getCell(e);
        mxGraphView view = graphComponent.getGraph().getView();
        mxCellState state = getStateToMark(view.getState(cell));

        return (state != null && intersects(state, e)) ? state : null;
    }

    /**
     * Returns the state at the given location. This uses mxGraph.getCellAt.
     */
    protected Object getCell(MouseEvent e) {
        return graphComponent.getCellAt(e.getX(), e.getY(),
                swimlaneContentEnabled);
    }

    /**
     * Returns the state to be marked for the given state under the mouse. This
     * returns the given state.
     */
    protected mxCellState getStateToMark(mxCellState state) {
        return state;
    }

    /**
     * Returns true if the given mouse event intersects the given state. This
     * returns true if the hotspot is 0 or the event is inside the hotspot for
     * the given cell state.
     */
    protected boolean intersects(mxCellState state, MouseEvent e) {
        if (isHotspotEnabled()) {
            return mxUtils.intersectsHotspot(state, e.getX(), e.getY(),
                    hotspot, mxConstants.MIN_HOTSPOT_SIZE,
                    mxConstants.MAX_HOTSPOT_SIZE);
        }

        return true;
    }

    /**
     * Adds the given event listener.
     */
    public void addListener(String eventName, mxIEventListener listener) {
        eventSource.addListener(eventName, listener);
    }

    /**
     * Removes the given event listener.
     */
    public void removeListener(mxIEventListener listener) {
        eventSource.removeListener(listener);
    }

    /**
     * Removes the given event listener for the specified event name.
     */
    public void removeListener(mxIEventListener listener, String eventName) {
        eventSource.removeListener(listener, eventName);
    }

    /**
     * Paints the outline of the markedState with the currentColor.
     */
    public void paint(Graphics g) {
        if (markedState != null && currentColor != null) {
            ((Graphics2D) g).setStroke(DEFAULT_STROKE);
            g.setColor(currentColor);

            if (markedState.getAbsolutePointCount() > 0) {
                Point last = markedState.getAbsolutePoint(0).getPoint();

                for (int i = 1; i < markedState.getAbsolutePointCount(); i++) {
                    Point current = markedState.getAbsolutePoint(i).getPoint();
                    g.drawLine(last.x - getX(), last.y - getY(), current.x
                            - getX(), current.y - getY());
                    last = current;
                }
            } else {
                g.drawRect(1, 1, getWidth() - 3, getHeight() - 3);
            }
        }
    }

}

  /* converted to utf8 */